package com.example.completionservice.controller;

import com.alibaba.fastjson.JSONObject;
import com.example.completionservice.callback.ExternalCallCallback;
import com.example.completionservice.callback.InsuranceVerificationCallback;
import com.example.completionservice.callback.TrialCalculationCallback;
import com.example.completionservice.callback.UnderwritingCheckCallback;
import com.example.completionservice.model.Insured;
import com.example.completionservice.model.PolicyModel;
import com.example.completionservice.model.Risk;
import com.example.completionservice.model.TaskResponseModel;
import com.example.completionservice.service.ExternalCallService;
import com.example.completionservice.service.InsuranceVerificationService;
import com.example.completionservice.service.TrialCalculationService;
import com.example.completionservice.service.UnderwritingCheckService;
import com.example.completionservice.task.TaskExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

/** 异步并发利器：实际项目中使用CompletionService提升系统性能的一次实践 https://blog.csdn.net/weixin_36380516/article/details/104305813
 * 业务
 * 投保业务主要涉及这几个大的方面：投保校验、核保校验、保费试算
 *      投保校验：最主要的是要查询客户黑名单和风险等级，都是千万级的表。而且投保人和被保人都需要校验
 *      核保校验：除了常规的核保规则校验，查询千万级的大表，还需要调用外部智能核保接口获得用户的风险等级，投保人和被保人都需要校验
 *      保费试算：需要计算每个险种的保费
 * 设计
 * 根据上面的业务，如果串行执行的话，单次性能肯定不高，所以考虑多线程异步执行获得校验结果，再对结果综合判断
 *      投保校验：采用一个线程(也可以根据投保人和被保人数量来采用几个线程)
 *      核保校验：
 *           常规校验：采用一个线程
 *           外部调用：有几个用户(指投保人和被保人)就采用几个线程
 *      保费计算：有几个险种就采用几个线程，最后合并得到整个的保费
 *
 * @date: 2020/1/17  11:19
 **/
@RestController
public class CompletionServiceController {

    //投保key
    private static final String KEY1_INSURANCE = "insurance_";
    //核保key
    private static final String KEY2_UNDERWRITING = "underwriting_";
    //外部调用key
    private static final String KEY3_EXTERNALCALL = "externalcall_";
    //试算key
    private static final String KEY4_TRIA = "trial_";

    private static final Logger logger = LoggerFactory.getLogger(CompletionServiceController.class);

    private final ExternalCallService externalCallService;
    private final InsuranceVerificationService insuranceVerificationService;
    private final TrialCalculationService trialCalculationService;
    private final UnderwritingCheckService underwritingCheckService;
    private final TaskExecutor taskExecutor;

    public CompletionServiceController(ExternalCallService externalCallService, InsuranceVerificationService insuranceVerificationService, TrialCalculationService trialCalculationService, UnderwritingCheckService underwritingCheckService, TaskExecutor taskExecutor) {
        this.externalCallService = externalCallService;
        this.insuranceVerificationService = insuranceVerificationService;
        this.trialCalculationService = trialCalculationService;
        this.underwritingCheckService = underwritingCheckService;
        this.taskExecutor = taskExecutor;
    }

    /**
      {
          "insuredList":[
              {
                  "idcard":"laza",
                  "name":"320106"
              },
              {
                  "idcard":"ranran",
                  "name":"120102"
              }
          ],
          "policyHolder":"lazasha",
          "policyNo":"345000987",
          "riskList":[
              {
                  "mainFlag":1,
                  "premium":300,
                  "riskcode":"risk001",
                  "riskname":"险种一"
              },
              {
                  "mainFlag":0,
                  "premium":400,
                  "riskcode":"risk002",
                  "riskname":"险种二"
              }
          ]
      }
     */

    //多线程异步并发接口
    @PostMapping(value = "/async", headers = "Content-Type=application/json;charset=UTF-8")
    public String asyncExec(@RequestBody PolicyModel policyModel) {
        long start = System.currentTimeMillis();

        asyncExecute(policyModel);
        logger.info("异步总共耗时：" + (System.currentTimeMillis() - start));
        return "ok";
    }

    //串行调用接口
    @PostMapping(value = "/sync", headers = "Content-Type=application/json;charset=UTF-8")
    public String syncExec(@RequestBody PolicyModel policyModel) {
        long start = System.currentTimeMillis();
        syncExecute(policyModel);
        logger.info("同步总共耗时：" + (System.currentTimeMillis() - start));
        return "ok";
    }

    //多线程异步并发接口
    private void asyncExecute(PolicyModel policyModel) {
        List<Callable<TaskResponseModel<Object>>> baseTaskCallbackList = new ArrayList<>();
        //根据被保人外部接口调用
        for (Insured insured : policyModel.getInsuredList()) {
            ExternalCallCallback externalCallCallback = new ExternalCallCallback(KEY3_EXTERNALCALL + insured.getIdcard(), insured, externalCallService);
            baseTaskCallbackList.add(externalCallCallback);
        }
        //投保校验
        InsuranceVerificationCallback insuranceVerificationCallback = new InsuranceVerificationCallback(KEY1_INSURANCE, policyModel, insuranceVerificationService);
        baseTaskCallbackList.add(insuranceVerificationCallback);
        //核保校验
        UnderwritingCheckCallback underwritingCheckCallback = new UnderwritingCheckCallback(KEY2_UNDERWRITING, policyModel, underwritingCheckService);
        baseTaskCallbackList.add(underwritingCheckCallback);
        //根据险种进行保费试算
        for(Risk risk : policyModel.getRiskList()) {
            TrialCalculationCallback trialCalculationCallback = new TrialCalculationCallback(KEY4_TRIA + risk.getRiskcode(), risk, trialCalculationService);
            baseTaskCallbackList.add(trialCalculationCallback);
        }
        List<TaskResponseModel<Object>> results = taskExecutor.execute(baseTaskCallbackList);
        for (TaskResponseModel<Object> t : results) {
            logger.info(t.toString());
        }

    }
    //串行调用接口
    private void syncExecute(PolicyModel policyModel) {
        //根据被保人外部接口调用
        for (Insured insured : policyModel.getInsuredList()) {
            TaskResponseModel<Object> externalCall = externalCallService.externalCall(insured.getIdcard(), insured);
            logger.info(externalCall.toString());
        }
        //投保校验
        TaskResponseModel<Object> insurance = insuranceVerificationService.insuranceCheck(KEY1_INSURANCE, policyModel);
        logger.info(insurance.toString());
        //核保校验
        TaskResponseModel<Object> underwriting = underwritingCheckService.underwritingCheck(KEY2_UNDERWRITING, policyModel);
        logger.info(underwriting.toString());
        //根据险种进行保费试算
        for(Risk risk : policyModel.getRiskList()) {
            TaskResponseModel<Object> risktrial = trialCalculationService.trialCalc(risk.getRiskcode(), risk);
            logger.info(risktrial.toString());
        }

    }

    public static void main(String[] args) {
        List<Risk> riskList = new ArrayList<>();
        Risk risk1 = new Risk();
        risk1.setMainFlag(1);
        risk1.setPremium(new BigDecimal("300"));
        risk1.setRiskcode("risk001");
        risk1.setRiskname("险种一");
        riskList.add(risk1);

        Risk risk2 = new Risk();
        risk2.setMainFlag(0);
        risk2.setPremium(new BigDecimal("400"));
        risk2.setRiskcode("risk002");
        risk2.setRiskname("险种二");
        riskList.add(risk2);

        List<Insured> insuredList = new ArrayList<>();
        Insured insured1 = new Insured("320106", "laza");
        insuredList.add(insured1);
        Insured insured2 = new Insured("120102", "ranran");
        insuredList.add(insured2);

        PolicyModel policyModel = new PolicyModel();
        policyModel.setInsuredList(insuredList);
        policyModel.setRiskList(riskList);
        policyModel.setPolicyNo("345000987");
        policyModel.setPolicyHolder("lazasha");
        System.out.println(JSONObject.toJSONString(policyModel));
    }
}
